package models;

import java.util.*;

import javax.persistence.*;

import play.db.jpa.*;
import play.modules.search.Field;
import play.modules.search.Indexed;

import models.Tag;

/**
 * 
 * @author Mostafa Fahim
 * @version 1.0
 * 
 */
@Entity
@Indexed
public class Topic extends Model {
	@Field
	public String name;
	public int status;
	/**
	 * 0 indicates ACTIVE, 1 indicates INACTIVE, 2 indicates CLOSED, 3 indicates
	 * REQUESTED, 4 indicates HIDDEN
	 */
	@Field
	@Lob
	public String description;
	public Date date;
	@ManyToOne
	public EEntity entity;
	@OneToMany(mappedBy = "topic", cascade = CascadeType.PERSIST)
	public List<Idea> ideas;
	@ManyToMany
	public List<ActionItem> items;
	@ManyToMany
	public List<Tag> tags;
	// @ManyToMany
	// public List<User> subscribers;
	@ManyToOne
	public User creator;
	public boolean isDraft;

	@ManyToMany
	@JoinTable(name = "UserFollowTopic")
	public List<User> followingUsers;

	public boolean isLock;
	@ManyToMany
	public List<Topic> relatedTopicList;

	@ManyToMany
	public List<TopicRelationNames> relationNames;

	@OneToMany(mappedBy = "topic", cascade = CascadeType.PERSIST)
	public List<TopicRating> usersRatings;
	@OneToMany(mappedBy = "sharedTopic", cascade = CascadeType.PERSIST)
	public List<SharedTopic> sharedTopics;
	public double topicRating;
	public int howMuchPopular;
	public int numberOfView;
	@ManyToMany
	public List<User> featuredFor;

	/**
	 * 
	 * @param name
	 *            name of the topic
	 * @param status
	 *            current status of the topic (active, not active ,blocked ...)
	 * @param description
	 *            description of the topic and what it discusses
	 * @param date
	 *            the date of creation of the topic
	 * @param entity
	 *            the entity under which the topic is created
	 * @param creator
	 *            the user who created the topic
	 */

	public Topic(String name, int status, String description, Date date,
			EEntity entity, User creator) {
		this.name = name;
		this.status = status;
		this.description = description;
		this.date = date;
		this.entity = entity;
		this.creator = creator;
		this.ideas = new ArrayList<Idea>();
		this.tags = new ArrayList<Tag>();
		this.items = new ArrayList<ActionItem>();
		this.isDraft = false;
		this.relatedTopicList = new ArrayList<Topic>();
		this.followingUsers = new ArrayList<User>();
		usersRatings = new ArrayList<TopicRating>();
		this.numberOfView = 0;
		this.relationNames = new ArrayList<TopicRelationNames>();
		featuredFor = new ArrayList<User>();
	}

	/**
	 * method to add new action item to the topic
	 * 
	 * @param itemId
	 *            id of the item to be added to this topic
	 * 
	 * @return the method return the Topic instance after the modifications
	 */

	public Topic addActionItem(long itemId) {
		ActionItem item = ActionItem.findById(itemId);
		item.addTopic(this.getId());
		this.items.add(item);
		this.save();
		return this;
	}

	/**
	 * This constructor is the same as the above but has isDraft which can
	 * enable the user to save the topic request as draft on creation.
	 * 
	 * @author Hesham Massoud
	 * 
	 * @param name
	 *            name of the topic
	 * @param status
	 *            current status of the topic (active, not active ,blocked ...)
	 * @param description
	 *            description of the topic and what it discusses
	 * @param date
	 *            the date of creation of the topic
	 * @param entity
	 *            the entity under which the topic is created
	 * @param creator
	 *            the user who created the topic
	 * @param isDraft
	 *            Checks whether a topic request is a draft or not. Always false
	 *            when topic does not have a status 'requested'
	 * @param isLock
	 *            Checks whether a topic is locked from relating or not.
	 */

	public Topic(String name, int status, String description, Date date,
			EEntity entity, User creator, boolean isDraft, boolean isLock) {
		this.name = name;
		this.status = status;
		this.description = description;
		this.date = date;
		this.entity = entity;
		this.creator = creator;
		this.ideas = new ArrayList<Idea>();
		this.tags = new ArrayList<Tag>();
		// this.subscribers = new ArrayList<User>();
		this.isDraft = isDraft;
		this.isLock = isLock;
		this.relatedTopicList = new ArrayList<Topic>();
		this.followingUsers = new ArrayList<User>();
		this.relationNames = new ArrayList<TopicRelationNames>();
	}

	public Topic(String name, int status, String description, Date date,
			EEntity entity, User creator, boolean isDraft) {
		this.name = name;
		this.status = status;
		this.description = description;
		this.date = date;
		this.entity = entity;
		this.creator = creator;
		// index = 0;
		this.ideas = new ArrayList<Idea>();
		this.tags = new ArrayList<Tag>();
		// this.subscribers = new ArrayList<User>();
		this.isDraft = isDraft;
		this.relatedTopicList = new ArrayList<Topic>();
		this.followingUsers = new ArrayList<User>();
		this.relationNames = new ArrayList<TopicRelationNames>();
	}

	/**
	 * Adds a tag to the topic (and the database). Ignores duplicate tags in
	 * case it found a duplicate tag in the topic. In case it finds a duplicate
	 * tag in another topic it uses this tag.
	 * 
	 * @author Shady
	 * @param tag
	 *            the tag to be added to the topic
	 * 
	 * @return true if the tag was successfully added, otherwise return false if
	 *         it found a duplicate tag in the same topic
	 */
	public boolean addTag(Tag tag) {

		for (Tag tmpTag : tags) {
			if (tmpTag.name.equals(tag.name) || tmpTag.id.equals(tag.id)) {
				return false;
			}
		}

		Tag tmpTag = Tag.find("byName", tag.name).first();

		if (tmpTag != null) {
			tags.add(tmpTag);
		} else {
			tags.add(tag);
			tag.save();
		}

		this.save();
		return true;
	}

	/**
	 * Removes a tag from the topic (and the database).
	 * 
	 * @author Shady
	 * @param tag
	 *            the tag to be removed from the topic and database
	 * 
	 * @return true if the remove was successful, otherwise throws exception
	 */
	public boolean removeTag(Tag tag) {

		tags.remove(tag);
		this.save();

		if (tag.relatedTopics.size() + tag.relatedIdeas.size()
				+ tag.relatedActionItems.size() == 0) {
			tag.delete();
		}

		return true;
	}

	/**
	 * Renames a tag in the topic (and the database).
	 * 
	 * @author Shady
	 * @param tag
	 *            the tag that will be renamed
	 * @param newName
	 *            the new name that the tag will be renamed to
	 * 
	 * @return true if successful else throws exception
	 */
	public boolean renameATag(Tag tag, String newName) {
		Tag tmpTag = Tag.find("LOWER(name)", newName.toLowerCase()).first();

		if (tmpTag != null)
			return false;

		this.tags.get(this.tags.indexOf(tag)).name = newName.toLowerCase();
		this.tags.get(this.tags.indexOf(tag)).save();
		this.save();
		return true;
	}

	/**
	 * The method takes a tag name and adds it as a tag to the given Topic
	 * baring the condition that the given user must be the organizer of the
	 * entity with which the topic belongs to, else it fails.
	 * 
	 * @author Shady
	 * @param userID
	 *            The organizer's ID
	 * @param entityID
	 *            The ID of the entity that the topic belongs to
	 * @param topicID
	 *            The topic ID that the tag will be added to
	 * 
	 * @return true if the tag was successfuly assigned else returns false
	 */
	public static boolean assignTag(long userID, long entityID, long topicID,
			String tagName) {
		// converting from ID to the real object
		User user;
		EEntity entity;
		Topic topic;

		try {
			user = User.findById(userID);
			entity = EEntity.findById(entityID);
			topic = Topic.findById(topicID);
		}

		catch (NullPointerException e) {
			System.out.println("Check the parameters!");
			e.getStackTrace();

			return false;
		}

		// check if the user is an organizer of the given entity
		List<User> entityOrganizers = entity.getOrganizers();

		if (!entityOrganizers.contains(user)) {
			return false;
		}

		// check if the topic belongs to the given entity

		if (topic.entity != entity) {
			return false;
		}

		// create the new tag to be inserted in the topic

		Tag tag = new Tag(tagName);

		// insert tag into topic

		if (topic.addTag(tag)) {
			return true;
		} else {
			return false;
		}

	}

	/**
	 * The method takes a tag ID and removes it (Tag) from the given Topic
	 * baring the condition that the given user must be the organizer of the
	 * entity with which the topic belongs to, else it fails.
	 * 
	 * @author Shady
	 * @param userID
	 *            The organizer's ID
	 * @param entityID
	 *            The ID of the entity that the topic belongs to
	 * @param topicID
	 *            The topic ID that the tag will be removed from
	 * 
	 * @return true if the tag was successfuly removed else returns false
	 */

	public static boolean unassignTag(long userID, long entityID, long topicID,
			long tagID) {
		// converting from ID to the real object
		User user;
		EEntity entity;
		Topic topic;
		Tag tag;

		try {
			user = User.findById(userID);
			entity = EEntity.findById(entityID);
			topic = Topic.findById(topicID);
			tag = Tag.findById(tagID);
		}

		catch (NullPointerException e) {
			System.out.println("Check the parameters!");
			e.getStackTrace();

			return false;
		}

		// check if the user is an Organizer/Organization Lead/has lead
		// privileges of the given entity

		if (entity.getOrganizers().contains(user)
				|| entity.organization.getOrganizationLead().equals(user)
				|| entity.organization.isAsOrganizationLead(user.id)
				|| user.isSysteAdmin()) {

			// check if the topic belongs to the given entity

			if (topic.entity != entity) {
				return false;
			}

			// check if the tag belongs to the given topic
			if (topic.tags.contains(tag)) {
				if (topic.removeTag(tag)) {
					return true;
				} else {

					return false;
				}
			} else {
				return false;
			}

		}

		return false;
	}

	/**
	 * The method renames a tag from the given Topic baring the condition that
	 * the given user must be the organizer of the entity with which the topic
	 * belongs to, else it fails.
	 * 
	 * @author Shady
	 * @param userID
	 *            The organizer's ID
	 * @param entityID
	 *            The ID of the entity that the topic belongs to
	 * @param topicID
	 *            The topic ID that the tag will be renamed in
	 * 
	 * @return true if the tag was successfuly renamed else returns false
	 * 
	 */

	public static boolean renameTag(long userID, long entityID, long topicID,
			long tagID, String newName) {
		// converting from ID to the real object
		User user;
		EEntity entity;
		Topic topic;
		Tag tag;

		try {
			user = User.findById(userID);
			entity = EEntity.findById(entityID);
			topic = Topic.findById(topicID);
			tag = Tag.findById(tagID);
		}

		catch (NullPointerException e) {
			System.out.println("Check the parameters!");
			e.getStackTrace();

			return false;
		}

		if (entity.getOrganizers().contains(user)
				|| entity.organization.getOrganizationLead().equals(user)
				|| entity.organization.isAsOrganizationLead(user.id)
				|| user.isSysteAdmin()) {

			// check if the topic belongs to the given entity
			if (topic.entity != entity) {
				return false;
			}

			// check if the tag belongs to the given topic
			if (topic.tags.contains(tag)) {
				if (topic.renameATag(tag, newName)) {
					return true;
				} else {
					return false;
				}

			} else {
				return false;
			}

		}

		return false;

	}

	/**
	 * A method to get a list of topics given tag.
	 * 
	 * @author Hesham Massoud
	 * 
	 * @param tag
	 *            The name of the tag that the method searches for topics by.
	 * @return A List of topics that either has a tag with the same name given
	 *         as parameter or a tag with a name that contains the given name.
	 */

	public static List<Topic> searchByTag(String tag) {
		List<Topic> listOfAllTopics = Topic.findAll();
		List<Topic> resultList = new ArrayList<Topic>();

		for (int i = 0; i < listOfAllTopics.size(); i++) {
			Topic topic = listOfAllTopics.get(i);
			for (int j = 0; j < topic.tags.size(); j++) {
				if (topic.tags.get(j).name.toLowerCase().contains(
						tag.toLowerCase())) {
					resultList.add(topic);
				}
			}
		}
		return resultList;
	}

	/**
	 * A method to add an idea to a topic
	 * 
	 * @author AhmedMustafaTaha
	 * 
	 * @param title
	 *            the title of the idea to be added.
	 * @param content
	 *            the content of the idea to be added.
	 * @param data
	 *            the date of the idea creation
	 * @param topic
	 *            the topic where the idea is posted
	 * @param user
	 *            the user how posted the idea
	 * @return the topic where the idea is posted
	 */

	public Topic addIdea(String title, String content, Date date, User creator) {
		creator.contribution++;
		creator.save();
		Idea newIdea = new Idea(title, content, date, this, creator).save();
		creator.createdIdeas.add(newIdea);
		creator.save();
		this.ideas.add(newIdea);
		this.save();
		LinkedList<User> toBeNotified = new LinkedList<User>();
		for (User user : this.followingUsers) {
			if (!toBeNotified.contains(user))
				toBeNotified.add(user);
		}
		for (User user : this.entity.followingUsers) {
			if (!toBeNotified.contains(user))
				toBeNotified.add(user);
		}
		for (User user : this.entity.organization.followingUsers) {
			if (!toBeNotified.contains(user))
				toBeNotified.add(user);
		}

		Notification newnot = new Notification(
				"A new idea has been added to topic " + this.name, newIdea.id,
				4, creator, toBeNotified);
		newnot.timeStamp = new Date();
		newnot.save();
		return this;
	}

	/**
	 * A method to save an idea as a draft.
	 * 
	 * @author Hesham Massoud
	 * 
	 * @param title
	 *            the title of the idea to be added.
	 * @param content
	 *            the content of the idea to be added.
	 * @param data
	 *            the date of the idea creation
	 * @param topic
	 *            the topic where the idea is posted
	 * @param user
	 *            the user who posted the idea
	 * @return the id of the idea posted
	 */
	public long saveIdeaAsDraft(String title, String content, Date date,
			Topic topic, User creator) {
		ArrayList<Plan> plans = new ArrayList<Plan>();
		Idea newIdea = new Idea(title, content, date, plans, topic, creator,
				true);
		newIdea.save();
		creator.createdIdeas.add(newIdea);
		creator.save();
		this.ideas.add(newIdea);
		this.save();
		return newIdea.id;
	}

	/**
	 * 
	 * @author Hesham Al-Razaz
	 * 
	 *         This Method is done to lock a Topic so that users can't relate to
	 *         it
	 * 
	 * @return it sets the state of the isLock to true (Topic Locked)
	 */

	public void lockTopic() {// This Method is done to lock a Topic so that
								// users can't
		// relate to it

		isLock = true;
		this.save();
	}

	/**
	 * @author Hesham Al-Razaz This Method is done to unlock a Topic so that
	 *         users can relate to it
	 * 
	 * @return it sets the state of the isLock to false (Topic unLocked)
	 */

	public void unlockTopic() {// This Method is done to unlock a Topic so that
								// users can
		// relate to it

		isLock = false;
		this.save();
	}

	/**
	 * @author Hesham Al-Razaz
	 * 
	 *         This Method is used to check the state of the lock
	 * 
	 * @return boolean variable for the state of the isLock
	 */

	public boolean checkLock() {

		return isLock;

	}

	/**
	 * the method is for adding a tag for topic
	 * 
	 * @param name
	 *            the name of the tag
	 * @return the tagged Topic
	 */

	public Topic addTag(String name) {
		Tag tag = Tag.find("byName", name).first();
		if (tag == null)
			tag = new Tag(name).save();
		for (int i = 0; i < tags.size(); i++) {
			if (tags.get(i).name == tag.name) {
				return this;
			}
		}
		tags.add(tag);
		return this;
	}

	/**
	 * @author AkrAmAtion
	 * 
	 * @return a String that will be used to export search results displaying a
	 *         topic whose name or description matches a keyword. The String is
	 *         formatted as Comma-Separated-Values
	 * */
	public String toString() {
		return "TOPIC," + name + "," + description + "," + creator + "," + date;
	}

	/**
	 * checks of the date of this topic posted on is since a week and if there
	 * was new ideas proposed on this topic or comments on its ideas.
	 * 
	 * @return boolean, true if the topic is active. false if the topic is
	 *         inactive.
	 * 
	 * @author Ahmed Mahmoud
	 */
	public boolean isActive() {
		List<Idea> ideas = this.ideas;
		for (Idea idea : ideas) {
			if (idea.isActive()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * it updates the overall rating of this topic.
	 * 
	 * @param newTopicRating
	 *            the new rating.
	 * @return Topic, the current instance.
	 * 
	 * @author Ahmed Mahmoud
	 */
	public Topic updateRating(double newTopicRating) {
		this.topicRating = newTopicRating;
		this.save();
		return this;
	}

	/**
	 * method to delete an idea from a topic
	 * 
	 * @author AhmedMustafaTaha
	 * 
	 * @param ideaID
	 *            the ID of the idea to be deleted
	 * @return the topic of the removed idea
	 */

	public Topic deleteIdea(long ideaID) {
		Idea idea = Idea.findById(ideaID);
		this.ideas.remove(idea);
		idea.creator.contribution--;
		idea.creator.createdIdeas.remove(idea);
		idea.creator.save();
		idea.cascadeDelete();
		this.save();
		return this;
	}

	/**
	 * @author Hesham Al-Razaz
	 * @param userTopic
	 *            one of the topics that is related
	 * @param relateTopic
	 *            one of the topics that is related
	 * @return the name of the relation
	 */
	public static String getTopicRelationName(Topic userTopic, Topic relateTopic) {
		int index = userTopic.relatedTopicList.indexOf(relateTopic);

		return userTopic.relationNames.get(index).name;
	}

	/**
	 * a method to change the current status of a topic to active or inactive by
	 * using method isActive
	 * 
	 * @author AhmedMustafaTaha
	 * 
	 * @return the topic to be changed
	 */

	public Topic showTopic() {
		if (this.isActive())
			this.status = 0;
		else
			this.status = 1;
		this.save();
		return this;
	}

	/**
	 * a method to change the current status of a topic to hidden
	 * 
	 * @author AhmedMustafaTaha
	 * 
	 * @return the changed Topic
	 */

	public Topic hideTopic() {
		this.status = 4;
		this.save();
		return this;
	}

	/**
	 * Returns a substring of the description of the topic with length maximum 40
	 * chars
	 * 
	 * @author Islam Al-Aarag
	 * @story C4S66
	 * @return String a substring of the description of the topic
	 */
	public String getSummarizedContent() {
		if (description.length() < 40)
			return description;
		else
			return description.substring(0, 40) + "...";
	}

	/**
	 * @author Ahmed Mahmoud
	 * @return
	 * @ManyToOne public EEntity entity;
	 * @OneToMany(mappedBy = "topic", cascade = CascadeType.PERSIST) public
	 *                     List<Idea> ideas;
	 * @ManyToMany public List<ActionItem> items;
	 * @ManyToMany public List<User> featuredFor;
	 */
	public boolean cascadeDelete() {
		while (!sharedTopics.isEmpty()) {
			SharedTopic sharedTopic = sharedTopics.get(0);
			sharedTopic.cascadeDelete();
		}

		while (!usersRatings.isEmpty()) {
			TopicRating topicRating = usersRatings.get(0);
			topicRating.cascadeDelete();
		}

		for (int i = 0; i < relatedTopicList.size(); i++) {
			Topic topic = relatedTopicList.get(i);
			topic.relatedTopicList.remove(this);
			topic.save();
		}
		for (int i = 0; i < followingUsers.size(); i++) {
			User user = followingUsers.get(i);
			user.followedTopics.remove(this);
			user.save();
		}
		for (int i = 0; i < tags.size(); i++) {
			Tag tag = tags.get(i);
			tag.relatedTopics.remove(this);
			tag.save();
		}

		creator.createdTopics.remove(this);
		creator.save();
		for (int i = 0; i < items.size(); i++) {
			ActionItem actionItem = items.get(i);
			actionItem.topics.remove(this);
			actionItem.save();
		}
		while (!ideas.isEmpty()) {
			Idea idea = ideas.get(0);
			idea.cascadeDelete();
		}

		for (User user : featuredFor) {
			user.featuredTopics.remove(this);
			user.save();
		}
		entity.topics.remove(this);
		entity.save();

		this.delete();
		return true;
	}

	public boolean isAllowedToBeViewedToIdeaDevelopers() {
		return !this.isDraft && !this.isLock && this.status < 2;
	}
}
